﻿using System;
using System.Collections.Generic;
using System.Text;

namespace BITUI.Tile
{
    public enum ColorBitLen
    {
        Bit1 = 1,//Mono
        Bit2 = 2,//=4color
        Bit4 = 4,//=16color
        Bit8 = 8,//=256color
    }
    public class TilePaletteData
    {
        public ColorBitLen bitLen;//1bit =mono, 2bit nes= 4 color ,4bit =16color , 8bit =256color
        //调色板，仅用于 1bit 2bit 4bit 模式，长度固定，1bit时paletteMap长度=2，2bit=4，4bit=16，8bit=0
        //256color模式时palette 0长度
        public byte[] palette;//
        //调色板置换映射表，仅用于256color模式，Key:tileColorIndex=>Value:bankColorIndex
        public Dictionary<byte, byte> paletteReplaceMap;//key,value,key,value,key,value,key,value,......
    }
    public class TileData
    {
        public ushort pixelwidth; //取值1~256 ,不允许0
        public ushort pixelheight;//取值1~256 ,不允许0
        public ColorBitLen bitLen;//1bit =mono, 2bit nes= 4 color ,4bit =16color , 8bit =256color
        public byte[] tiledata;

        public TilePaletteData palette;

        public static TileData Create(ColorBitLen bit, ushort pixelwidth, ushort pixelheight)
        {
            if (pixelheight == 0 || pixelwidth == 0 || pixelheight > 256 || pixelwidth > 256)
                throw new Exception("error tile pixel size");
            TileData data = new TileData();
            data.bitLen = bit;
            data.pixelwidth = pixelwidth;
            data.pixelheight = pixelheight;
            data.CalcPixelWidthAndPaletteSize(out ushort bytewidth, out ushort paletteLen);
            if (paletteLen > 0)
            {
                data.palette = new TilePaletteData();
                data.palette.bitLen = bit;
                data.palette.palette = new byte[paletteLen];
            }
            data.tiledata = new byte[bytewidth * pixelheight];
            return data;
        }
        private void CalcPixelWidthAndPaletteSize(out ushort bytewidth, out ushort paletteLen)
        {
            bytewidth = pixelwidth;
            paletteLen = 0;
            if (bitLen == ColorBitLen.Bit1)
            {
                bytewidth = (byte)(pixelwidth / 8);
                if (pixelwidth % 8 != 0)
                    bytewidth++;
                paletteLen = 2;

            }
            else if (bitLen == ColorBitLen.Bit2)
            {
                bytewidth = (byte)(pixelwidth / 4);
                if (pixelwidth % 4 != 0)
                    bytewidth++;
                paletteLen = 4;

            }
            else if (bitLen == ColorBitLen.Bit4)
            {
                bytewidth = (byte)(pixelwidth / 2);
                if (pixelwidth % 2 != 0)
                    bytewidth++;
                paletteLen = 16;
            }
            else
            {
                throw new Exception("error format.");
            }
        }
        public void Read(System.IO.Stream stream)
        {
            bitLen = (ColorBitLen)stream.ReadByte();
            pixelwidth = (ushort)(stream.ReadByte() + 1);
            pixelheight = (ushort)(stream.ReadByte() + 1);
            CalcPixelWidthAndPaletteSize(out ushort bytewidth, out ushort paletteLen);

            this.tiledata = new byte[bytewidth * pixelheight];

            stream.Read(tiledata, 0, bytewidth * pixelheight);

            //调色板
            if (paletteLen > 0)
            {
                this.palette = new TilePaletteData();
                this.palette.bitLen = bitLen;
                this.palette.palette = new byte[paletteLen];
                stream.Read(palette.palette, 0, paletteLen);
            }
            //只有256色有调色板置换
            if (bitLen == ColorBitLen.Bit8)
            {
                var maplen = (byte)stream.ReadByte();
                if (maplen > 0)
                {
                    if (this.palette == null)
                    {
                        this.palette = new TilePaletteData();
                        this.palette.bitLen = bitLen;
                        this.palette.palette = null;
                    }
                    var data = new byte[maplen * 2];
                    stream.Read(data, 0, maplen * 2);
                    this.palette.paletteReplaceMap = new Dictionary<byte, byte>();
                    for (var i = 0; i < maplen; i++)
                    {
                        this.palette.paletteReplaceMap[data[i * 2 + 0]] = data[i * 2 + 1];
                    }

                }
            }
        }
        public byte[] ToBytes()
        {
            using (var ms = new System.IO.MemoryStream())
            {
                this.Write(ms);
                return ms.ToArray();
            }
        }
        public void Write(System.IO.Stream stream)
        {
            stream.WriteByte((byte)bitLen);
            stream.WriteByte((byte)(pixelwidth - 1));
            stream.WriteByte((byte)(pixelheight - 1));

            CalcPixelWidthAndPaletteSize(out ushort bytewidth, out ushort paletteLen);

            //数据区


            stream.Write(tiledata, 0, bytewidth * pixelheight);

            //调色板
            if (paletteLen > 0)
            {
                stream.Write(palette.palette, 0, paletteLen);
            }

            //只有256色有调色板置换
            if (bitLen == ColorBitLen.Bit8)
            {
                int count = palette.paletteReplaceMap != null ? palette.paletteReplaceMap.Count : 0;
                if (count > 255)
                    throw new Exception("too much palette");
                var maplen = (byte)(count);
                stream.WriteByte(maplen);
                if (maplen > 0)
                {
                    foreach (var item in palette.paletteReplaceMap)
                    {
                        stream.WriteByte(item.Key);
                        stream.WriteByte(item.Value);
                    }
                }
            }

        }


        public byte[] GetLine(int line)
        {
            byte[] bytesline = new byte[pixelwidth];
            if (this.bitLen == ColorBitLen.Bit1)
            {
                int bytewidth = this.pixelwidth / 8;
                if (this.pixelwidth % 8 != 0)
                {
                    bytewidth++;
                }
                for (var x = 0; x < this.pixelwidth; x += 8)
                {
                    var srcdata = this.tiledata[line * bytewidth + x / 8];
                    if (x + 0 < pixelwidth) bytesline[x + 0] = (byte)(srcdata >> 7 & 0x1);
                    if (x + 1 < pixelwidth) bytesline[x + 1] = (byte)(srcdata >> 6 & 0x1);
                    if (x + 2 < pixelwidth) bytesline[x + 2] = (byte)(srcdata >> 5 & 0x1);
                    if (x + 3 < pixelwidth) bytesline[x + 3] = (byte)(srcdata >> 4 & 0x1);
                    if (x + 4 < pixelwidth) bytesline[x + 4] = (byte)(srcdata >> 3 & 0x1);
                    if (x + 5 < pixelwidth) bytesline[x + 5] = (byte)(srcdata >> 2 & 0x1);
                    if (x + 6 < pixelwidth) bytesline[x + 6] = (byte)(srcdata >> 1 & 0x1);
                    if (x + 7 < pixelwidth) bytesline[x + 7] = (byte)(srcdata >> 0 & 0x1);
                }
            }
            else if (this.bitLen == ColorBitLen.Bit2)
            {
                int bytewidth = this.pixelwidth / 4;
                if (this.pixelwidth % 4 != 0)
                {
                    bytewidth++;
                }
                for (var x = 0; x < this.pixelwidth; x += 4)
                {
                    var srcdata = this.tiledata[line * bytewidth + x / 4];
                    bytesline[x + 0] = (byte)(srcdata >> 6 & 0x3);
                    bytesline[x + 1] = (byte)(srcdata >> 4 & 0x3);
                    bytesline[x + 2] = (byte)(srcdata >> 2 & 0x3);
                    bytesline[x + 3] = (byte)(srcdata >> 0 & 0x3);
                }
            }
            else if (this.bitLen == ColorBitLen.Bit4)
            {
                int bytewidth = this.pixelwidth / 2;
                if (this.pixelwidth % 2 != 0)
                {
                    bytewidth++;
                }
                for (var x = 0; x < this.pixelwidth; x += 2)
                {
                    var srcdata = this.tiledata[line * bytewidth + x / 2];
                    bytesline[x + 0] = (byte)(srcdata >> 4 & 0x0f);
                    bytesline[x + 1] = (byte)(srcdata >> 0 & 0x0f);
                }
            }
            else if (this.bitLen == ColorBitLen.Bit8)
            {
                for (var x = 0; x < this.pixelwidth; x++)
                {
                    bytesline[x] = this.tiledata[line * this.pixelwidth + x];
                }
            }
            return bytesline;
        }

        public byte[] GetLineWithPalette(int line, TilePaletteData _palette = null)
        {
            if (_palette == null)
                _palette = this.palette;
            byte[] bytesline = GetLine(line);
            if (this.bitLen != ColorBitLen.Bit8)
            {
                for (var i = 0; i < bytesline.Length; i++)
                {
                    bytesline[i] = _palette.palette[bytesline[i]];
                }
            }
            else
            {
                if (_palette != null && _palette.paletteReplaceMap != null && _palette.paletteReplaceMap.Count > 0)
                {
                    for (var i = 0; i < bytesline.Length; i++)
                    {
                        bytesline[i] = _palette.paletteReplaceMap[bytesline[i]];
                    }
                }
            }
            return bytesline;
        }
        public void SetLine(int line, byte[] data)
        {
            this.CalcPixelWidthAndPaletteSize(out ushort bytewidth, out ushort palettelen);
            if (bytewidth > data.Length)
                throw new Exception("data is not enough.");
            if (bitLen == ColorBitLen.Bit1)
            {
                for (var x = 0; x < this.pixelwidth; x += 8)
                {
                    byte color = (byte)(
                        ((data[x + 0] & 1) << 7) |
                        ((data[x + 1] & 1) << 6) |
                        ((data[x + 2] & 1) << 5) |
                        ((data[x + 3] & 1) << 4) |
                        ((data[x + 4] & 1) << 3) |
                        ((data[x + 5] & 1) << 2) |
                        ((data[x + 6] & 1) << 1) |
                        ((data[x + 7] & 1) << 0)
                        );
                    this.tiledata[line * bytewidth + x / 8] = color;
                }
            }
            else if (bitLen == ColorBitLen.Bit2)
            {
                for (var x = 0; x < this.pixelwidth; x += 4)
                {
                    byte color = (byte)(
                        ((data[x + 0] & 3) << 6) |
                        ((data[x + 1] & 3) << 4) |
                        ((data[x + 2] & 3) << 2) |
                        ((data[x + 3] & 3) << 0)
                        );
                    this.tiledata[line * bytewidth + x / 4] = color;
                }
            }
            else if (bitLen == ColorBitLen.Bit4)
            {
                for (var x = 0; x < this.pixelwidth; x += 2)
                {
                    byte color = (byte)(
                        ((data[x + 0] & 0x0f) << 4) |
                        ((data[x + 1] & 0x0f) << 0)
                        );
                    this.tiledata[line * bytewidth + x / 2] = color;
                }
            }
            else if (bitLen == ColorBitLen.Bit8)
            {
                for (var x = 0; x < this.pixelwidth; x += 1)
                {
                    byte color = data[x];
                    this.tiledata[line * bytewidth + x] = color;
                }
            }
        }
        public void SetLine(int line, bool[] data)
        {
            if (bitLen == ColorBitLen.Bit1)
            {
                byte[] bdata = new byte[data.Length];
                for (var i = 0; i < bdata.Length; i++)
                    bdata[i] = data[i] ? (byte)1 : (byte)0;
                SetLine(line, bdata);
            }
            else
            {
                throw new Exception("not for this.");
            }
        }
    }
}
